﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Bio.Data.Interfaces;
using System.Xml.Linq;
using System.Diagnostics;
using Bio.Data.Providers.Structure;

namespace Bio.Data.Providers.rCAD.Structure.Models
{
    class DbStructureModelBioEntity : BioExtensiblePropertyBase, IStructureModelBioEntity
    {
        #region IStructureModelBioEntity Members

        public IBioSequence Sequence 
        {
            get { return DBStructureModelObjProxy.Sequence; }
        }

        public IList<IBasePairBioEntity> BasePairs 
        {
            get { return DBStructureModelObjProxy.BasePairs; }
        }

        public IList<IStructureExtentBioEntity> Extents 
        {
            get { return DBStructureModelObjProxy.Extents; }
        }

        #endregion

        //Want to pass some kind of "empty" sequence object/sequence metadata here.
        public DbStructureModelBioEntity(rcadDataContext dc, int sequenceID, string scientificName, int alignmentID)
        {
            if(InitializeModel(dc, sequenceID, alignmentID, scientificName))
            {
                //here we'll load the rendering if its available.
            }
            else
            {
                throw new ArgumentException(string.Format("Error: Failed to Load Structure Model for Sequence {0} from Alignment {1}", sequenceID, alignmentID));
            }
        }

        private RNASecondaryStructureModel DBStructureModelObjProxy { get; set; }
       
        //We load the sequence, pairs and extents first. The sequence comes from the alignment.
        private bool InitializeModel(rcadDataContext dc, int sequenceID, int alignmentID, string scientificName)
        {
            if (dc == null)
                return false;

            var seqvalues = from ntentry in dc.vAlignmentGridUngappeds
                            where ntentry.SeqID == sequenceID && ntentry.AlnID == alignmentID
                            orderby ntentry.SequenceIndex ascending
                            select ntentry;

            SimpleRNASequence dbsequence = new SimpleRNASequence() { Id = sequenceID.ToString(), ScientificName = scientificName, SourceId = alignmentID.ToString() };
            foreach (var nt in seqvalues)
                dbsequence.SequenceData.Add(new BioSymbol(BioSymbolType.Nucleotide, nt.BioSymbol));

            DBStructureModelObjProxy = new RNASecondaryStructureModel(dc.Connection.ToString(), dbsequence);

            //Load Base Pairs
            var pairdbentries = from pair in dc.SecondaryStructureBasePairs
                              where pair.SeqID == sequenceID && pair.AlnID == alignmentID
                              select pair;

            foreach (var pair in pairdbentries)
            {
                RNASecondaryStructureBasePair bpobject = new RNASecondaryStructureBasePair(dbsequence, pair.FivePrimeElementSequenceIndex - 1, pair.ThreePrimeElementSequenceIndex - 1);
                DBStructureModelObjProxy.BasePairs.Add(bpobject);
            }

            //Load Extents
            var extentdbentries = from extent in dc.SecondaryStructureExtents
                                  join exttype in dc.SecondaryStructureExtentTypes
                                  on extent.ExtentTypeID equals exttype.ExtentTypeID
                                  where extent.SeqID == sequenceID && extent.AlnID == alignmentID
                                  select new { extent, exttype };

            foreach (var extentrow in extentdbentries)
            {
                StructureExtentType extenttype = (StructureExtentType)Enum.Parse(typeof(StructureExtentType), extentrow.exttype.ExtentType, true);
                RNASecondaryStructureExtent bpextent = new RNASecondaryStructureExtent(dbsequence, extentrow.extent.ExtentID, extentrow.extent.ExtentOrdinal, extenttype, extentrow.extent.ExtentStartIndex - 1, extentrow.extent.ExtentEndIndex - 1);
                DBStructureModelObjProxy.Extents.Add(bpextent);
            }

            var rendering = dc.SecondaryStructureDrawings.Where(row => (row.SeqID == sequenceID && row.AlnID == alignmentID)).FirstOrDefault();

            if (rendering == null)
            {
                return true; //no more data to load.
            }
            else
            {
                return LoadRendering(rendering.Rendering);
            }
        }

        private bool LoadRendering(XElement structureModelRendering)
        {
            if (structureModelRendering == null)
                return false;

            //1. Check if the model has rendering or not.
            XAttribute renderingAttribute = structureModelRendering.Attribute(RNAStructureModelXMLTags.StructureModelHasRenderingAttribute);
            bool hasRendering = false;
            if (renderingAttribute == null)
                return false;
            if (!bool.TryParse(renderingAttribute.Value, out hasRendering))
                return false;
            if (!hasRendering)
                return false;

            //2. Check the sequence length attribute.
            XAttribute sequenceLengthAttribute = structureModelRendering.Attribute(RNAStructureModelXMLTags.StructureModelSequenceLengthAttribute);
            int sequenceLengthVal = 0;
            if (sequenceLengthAttribute == null)
                return false;
            if (!Int32.TryParse(sequenceLengthAttribute.Value, out sequenceLengthVal))
                return false;

            //3. Parse the sequence and check that it matches the expected length and the sequence in the alignment (which is already loaded in the model at this point)
            if (!HandleStructureModelSequence(structureModelRendering.Element(RNAStructureModelXMLTags.StructureModelSequenceLabel), sequenceLengthVal))
                return false;

            //4. Parse the rendering map for the sequence
            if (!HandleSequenceRendering(structureModelRendering.Element(RNAStructureModelXMLTags.StructureModelRenderingLabel)))
                return false;

            //5. Parse properties specified at the structure model level
            XElement structureModelNucleotideProperties = structureModelRendering.Element(RNAStructureModelXMLTags.StructureModelPropertiesLabel);
            if (structureModelNucleotideProperties != null)
            {
                RNASecondaryStructureNucleotideTextShape smntshapeprops = RNASecondaryStructureNucleotideTextShape.FromXML(structureModelNucleotideProperties);
                RNASecondaryStructureNucleotideCircleShape smntcircleshapeprops = RNASecondaryStructureNucleotideCircleShape.FromXML(structureModelNucleotideProperties);
                var strmdlmetadataglobalntrenderingmode = structureModelNucleotideProperties.Element(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel);
                if (!DoesExtendedPropertyExist(StructureModelMetadata.Key))
                {
                    StructureModelMetadata smmetadata = new StructureModelMetadata();
                    SetExtendedProperty(StructureModelMetadata.Key, smmetadata);
                }
                GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideTextRendering = smntshapeprops;
                GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideCircleRendering = smntcircleshapeprops;
                NucleotideMetadata.RenderingMode globalntrenderingmode;
                if (strmdlmetadataglobalntrenderingmode != null && !strmdlmetadataglobalntrenderingmode.IsEmpty &&
                    Enum.TryParse(strmdlmetadataglobalntrenderingmode.Value, out globalntrenderingmode))
                    GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideRenderingMode = globalntrenderingmode;

                RNASecondaryStructureBasePairConnector smconnshapeprops = RNASecondaryStructureBasePairConnector.FromXML(structureModelNucleotideProperties);
                var strmdlmetadataglobalbpconnectorrenderingmode = structureModelNucleotideProperties.Element(RNAStructureModelXMLTags.StructureModelPairConnectorRenderingStyleLabel);
                BasePairMetadata.ConnectorRenderingMode globalbpconnrenderingmode;
                if (strmdlmetadataglobalbpconnectorrenderingmode != null && !strmdlmetadataglobalbpconnectorrenderingmode.IsEmpty &&
                    Enum.TryParse<BasePairMetadata.ConnectorRenderingMode>(strmdlmetadataglobalbpconnectorrenderingmode.Value, out globalbpconnrenderingmode))
                    GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).ConnectorStyle = globalbpconnrenderingmode;

                bool globalbpconnvisibility;
                var strmdlmetadataglobalbpconnectorvisibility = structureModelNucleotideProperties.Element(RNAStructureModelXMLTags.StructureModelPairConnectorVisibleLabel);
                if (strmdlmetadataglobalbpconnectorvisibility != null && !strmdlmetadataglobalbpconnectorvisibility.IsEmpty &&
                    bool.TryParse(strmdlmetadataglobalbpconnectorvisibility.Value, out globalbpconnvisibility))
                    GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).ConnectorVisible = globalbpconnvisibility;
            }

            //6. Parse any properties specified at the nucleotide level
            XElement individualNucleotideProperties = structureModelRendering.Element(RNAStructureModelXMLTags.StructureModelNucleotidePropertiesLabel);
            if (individualNucleotideProperties != null)
            {
                HandleNucleotideProperties(individualNucleotideProperties);
            }

            //7. Parse any properties specified at the base pair level
            XElement pairs = structureModelRendering.Element(RNAStructureModelXMLTags.StructureModelBasePairsLabel);
            HandleBasePairProperties(pairs);
            return true;
        }

        private bool HandleStructureModelSequence(XElement sequenceElement, int expectedSeqLength)
        {
            if (sequenceElement != null && sequenceElement.Name.LocalName.Equals(RNAStructureModelXMLTags.StructureModelSequenceLabel))
            {
                if (expectedSeqLength != DBStructureModelObjProxy.Sequence.SequenceData.Count)
                    return false;

                var seqFromAlignment = new StringBuilder();
                foreach (var symbol in DBStructureModelObjProxy.Sequence.SequenceData)
                    seqFromAlignment.Append(symbol.Value);

                if (!sequenceElement.Value.Equals(seqFromAlignment.ToString()))
                    return false;

                foreach (var nt in DBStructureModelObjProxy.Sequence.SequenceData)
                {
                    IExtensibleProperties ntprops = (IExtensibleProperties)nt;
                    if (nt != null)
                    {
                        NucleotideMetadata ntMetadata = new NucleotideMetadata() { Visible = true };
                        ntprops.SetExtendedProperty(NucleotideMetadata.Key, ntMetadata);
                    }
                    else
                    {
                        return false; //Can't add extensible properties on these sequence elements for some reason.
                    }
                }
            }
            else
            {
                return false; //No sequence associated with the structure model
            }

            return true;
        }

        private bool HandleSequenceRendering(XElement sequenceRenderingElement)
        {
            if (sequenceRenderingElement != null && sequenceRenderingElement.Name.LocalName.Equals(RNAStructureModelXMLTags.StructureModelRenderingLabel))
            {
                SequenceRendering rendering = new SequenceRendering();
                var renderingVectors = from vector in sequenceRenderingElement.Descendants(RNAStructureModelXMLTags.StructureModelRenderingVectorLabel)
                                       select vector;
                foreach (var vector in renderingVectors)
                {
                    RNASecondaryStructureNucleotideVector ntVector = RNASecondaryStructureNucleotideVector.FromXML(vector);
                    if (ntVector != null)
                    {
                        if (ntVector.NtIndex < 0 || ntVector.NtIndex > DBStructureModelObjProxy.Sequence.SequenceData.Count)
                        {
                            return false;
                        }
                        else
                        {
                            rendering.RenderingMap[ntVector.NtIndex] = ntVector;   
                        }
                    }
                    else
                    {
                        return false; //Rendering vector is incorrectly formatted.
                    }
                }

                //Compute the rendering vectors since they are not stored in the file
                var allNt = rendering.RenderingMap.Keys.OrderBy(k => k);
                foreach (var nt in allNt)
                {
                    int nextNt = (nt + 1);
                    if (nextNt < rendering.RenderingMap.Keys.Count)
                    {
                        IVector ntVector = new RNASecondaryStructureNucleotideVector(rendering.RenderingMap[nt].StartX, rendering.RenderingMap[nt].StartY,
                            rendering.RenderingMap[nextNt].StartX, rendering.RenderingMap[nextNt].StartY);
                        rendering.RenderingMap[nt] = ntVector;
                    }
                }

                if (rendering.RenderingMap.Count != DBStructureModelObjProxy.Sequence.SequenceData.Count) //Again, some error in the rendering map where we don't have enough 
                {
                    return false;
                }

                IExtensibleProperties seqprops = (IExtensibleProperties)DBStructureModelObjProxy.Sequence;
                if (seqprops != null)
                {
                    if (!seqprops.DoesExtendedPropertyExist(SequenceMetadata.Key))
                    {
                        SequenceMetadata metadata = new SequenceMetadata();
                        seqprops.SetExtendedProperty(SequenceMetadata.Key, metadata);
                    }
                    seqprops.GetExtendedProperty<SequenceMetadata>(SequenceMetadata.Key).Data.Add(new KeyValuePair<string, object>(SequenceRendering.Key, rendering));
                }
                else
                {
                    return false; //No ability to store the rendering information as metadata.
                }
            }
            else
            {
                return false; //No rendering information, but rendering attribute set to true?
            }

            return true;
        }

        private void HandleNucleotideProperties(XElement nucleotidePropertiesBlock)
        {
            if (nucleotidePropertiesBlock != null && nucleotidePropertiesBlock.Name.LocalName.Equals(RNAStructureModelXMLTags.StructureModelNucleotidePropertiesLabel))
            {
                var nucleotidePropSet = from ntprops in nucleotidePropertiesBlock.Descendants(RNAStructureModelXMLTags.StructureModelNucleotideLabel)
                                        select ntprops;
                foreach (var ntprop in nucleotidePropSet)
                {
                    XElement ntIndexElement = ntprop.Element(RNAStructureModelXMLTags.StructureModelNucleotideIndexLabel);
                    int ntIndex = 0;
                    if (ntIndexElement != null && Int32.TryParse(ntIndexElement.Value, out ntIndex)) //If we don't have a valid index specified, we can't apply the properties
                    {
                        //Have to decrement since strml is 1s based.
                        ntIndex = ntIndex - 1;
                        IExtensibleProperties ntprops = (IExtensibleProperties)DBStructureModelObjProxy.Sequence.SequenceData[ntIndex];
                        if (ntprops != null)
                        {
                            NucleotideMetadata ntMetadata = ntprops.GetExtendedProperty<NucleotideMetadata>(NucleotideMetadata.Key);
                            if (ntMetadata == null)
                            {
                                ntMetadata = new NucleotideMetadata();
                                ntprops.SetExtendedProperty(NucleotideMetadata.Key, ntMetadata);
                            }

                            NucleotideMetadata.RenderingMode localNtStyle;
                            var ntpropblockstyle = ntprop.Element(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel);
                            if (ntpropblockstyle != null &&
                                !ntpropblockstyle.IsEmpty &&
                                Enum.TryParse(ntpropblockstyle.Value, out localNtStyle))
                                ntMetadata.Style = localNtStyle;

                            bool localNtVisibility;
                            var ntpropblockvis = ntprop.Element(RNAStructureModelXMLTags.StructureModelNucleotideVisibilityLabel);
                            if (ntpropblockvis != null &&
                                !ntpropblockvis.IsEmpty &&
                                bool.TryParse(ntpropblockvis.Value, out localNtVisibility))
                                ntMetadata.Visible = localNtVisibility;

                            bool localNtIsSubstituted = false;
                            char localNtSubstitution = ' ';
                            var ntpropblocsubstitution = ntprop.Element(RNAStructureModelXMLTags.StructureModelNucleotideSubstitutionLabel);
                            if (ntpropblocsubstitution != null &&
                                !ntpropblocsubstitution.IsEmpty)
                            {
                                if (ntpropblocsubstitution.Value.Length >= 1)
                                {
                                    localNtIsSubstituted = true;
                                    localNtSubstitution = ntpropblocsubstitution.Value[0];
                                }
                            }

                            RNASecondaryStructureNucleotideTextShape ntTextRendering = RNASecondaryStructureNucleotideTextShape.FromXML(ntprop);
                            RNASecondaryStructureNucleotideCircleShape ntCircleRendering = RNASecondaryStructureNucleotideCircleShape.FromXML(ntprop);

                            ntMetadata.IsSubstituted = localNtIsSubstituted;
                            ntMetadata.SubstitutionCharacter = localNtSubstitution;
                            ntMetadata.CircleRendering = ntCircleRendering;
                            ntMetadata.TextRendering = ntTextRendering;
                        }
                    }
                }
            }
        }

        private void HandleBasePairProperties(XElement pairsBlock)
        {
            if(pairsBlock != null && pairsBlock.Name.LocalName.Equals(RNAStructureModelXMLTags.StructureModelBasePairsLabel))
            {
                var basePairSet = pairsBlock.Descendants(RNAStructureModelXMLTags.StructureModelPairLabel);
                foreach(var basePair in basePairSet)
                {
                    int fivePrimeIndex = -1;
                    int threePrimeIndex = -1;
                    var fivePrimeIdxElement = basePair.Element(RNAStructureModelXMLTags.StructureModelPairFivePrimeIndexLabel);
                    var threePrimeIdxElement = basePair.Element(RNAStructureModelXMLTags.StructureModelPairThreePrimeIndexLabel);

                    if (fivePrimeIdxElement != null &&
                        !fivePrimeIdxElement.IsEmpty &&
                        Int32.TryParse(fivePrimeIdxElement.Value, out fivePrimeIndex) &&
                        threePrimeIdxElement != null &&
                        !threePrimeIdxElement.IsEmpty &&
                        Int32.TryParse(threePrimeIdxElement.Value, out threePrimeIndex))
                    {
                        //Have to decrement since the file is 1s based.
                        fivePrimeIndex = fivePrimeIndex - 1;
                        threePrimeIndex = threePrimeIndex - 1;

                        IBasePairBioEntity bpEntity = BasePairs.Where(bp => (bp.FivePrimeIndex == fivePrimeIndex && bp.ThreePrimeIndex == threePrimeIndex)).FirstOrDefault();
                        if (bpEntity != null)
                        {
                            IExtensibleProperties bpProps = bpEntity as IExtensibleProperties;
                            if (bpProps != null)
                            {
                                BasePairMetadata bpMetadata = null;

                                var strmdlpairconnectorvisible = basePair.Element(RNAStructureModelXMLTags.StructureModelPairConnectorVisibleLabel);
                                bool bConnVisible = true;
                                if (strmdlpairconnectorvisible != null &&
                                    !strmdlpairconnectorvisible.IsEmpty &&
                                    bool.TryParse(strmdlpairconnectorvisible.Value, out bConnVisible))
                                {
                                    if (bpMetadata == null)
                                        bpMetadata = new BasePairMetadata();

                                    bpMetadata.ConnectorVisible = bConnVisible;
                                }

                                var strmdlpairconnectorrenderingstyle = basePair.Element(RNAStructureModelXMLTags.StructureModelPairConnectorRenderingStyleLabel);
                                BasePairMetadata.ConnectorRenderingMode connRenderingStyle;
                                if(strmdlpairconnectorrenderingstyle != null &&
                                    !strmdlpairconnectorrenderingstyle.IsEmpty &&
                                    Enum.TryParse<BasePairMetadata.ConnectorRenderingMode>(strmdlpairconnectorrenderingstyle.Value, out connRenderingStyle))
                                {
                                    if (bpMetadata == null)
                                        bpMetadata = new BasePairMetadata(); 
                                        
                                    bpMetadata.ConnectorStyle = connRenderingStyle;
                                }

                                var strmdleconnectorproperties = basePair.Element(RNAStructureModelXMLTags.StructureModelPairConnectorLabel);
                                if (strmdleconnectorproperties != null &&
                                    !strmdleconnectorproperties.IsEmpty)
                                {
                                    if (bpMetadata == null)
                                        bpMetadata = new BasePairMetadata();

                                    bpMetadata.ConnectorRendering = RNASecondaryStructureBasePairConnector.FromXML(strmdleconnectorproperties);
                                }

                                var strmdlpairntproperties = basePair.Element(RNAStructureModelXMLTags.StructureModelPairNucleotideLabel);
                                if (strmdlpairntproperties != null &&
                                    !strmdlpairntproperties.IsEmpty)
                                {
                                    if (bpMetadata == null)
                                        bpMetadata = new BasePairMetadata();

                                    bpMetadata.NucleotideRenderingProperties = new NucleotideMetadata();

                                    NucleotideMetadata.RenderingMode localNtStyle;
                                    var ntpropblockstyle = strmdlpairntproperties.Element(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel);
                                    if (ntpropblockstyle != null &&
                                        !ntpropblockstyle.IsEmpty &&
                                        Enum.TryParse(ntpropblockstyle.Value, out localNtStyle))
                                        bpMetadata.NucleotideRenderingProperties.Style = localNtStyle;

                                    bool localNtVisibility;
                                    var ntpropblockvis = strmdlpairntproperties.Element(RNAStructureModelXMLTags.StructureModelNucleotideVisibilityLabel);
                                    if (ntpropblockvis != null &&
                                        !ntpropblockvis.IsEmpty &&
                                        bool.TryParse(ntpropblockvis.Value, out localNtVisibility))
                                        bpMetadata.NucleotideRenderingProperties.Visible = localNtVisibility;

                                    bool localNtIsSubstituted = false;
                                    char localNtSubstitution = ' ';
                                    var ntpropblocsubstitution = strmdlpairntproperties.Element(RNAStructureModelXMLTags.StructureModelNucleotideSubstitutionLabel);
                                    if (ntpropblocsubstitution != null &&
                                        !ntpropblocsubstitution.IsEmpty)
                                    {
                                        if (ntpropblocsubstitution.Value.Length >= 1)
                                        {
                                            localNtIsSubstituted = true;
                                            localNtSubstitution = ntpropblocsubstitution.Value[0];
                                        }
                                    }

                                    RNASecondaryStructureNucleotideTextShape ntTextRendering = RNASecondaryStructureNucleotideTextShape.FromXML(strmdlpairntproperties);
                                    RNASecondaryStructureNucleotideCircleShape ntCircleRendering = RNASecondaryStructureNucleotideCircleShape.FromXML(strmdlpairntproperties);

                                    bpMetadata.NucleotideRenderingProperties.IsSubstituted = localNtIsSubstituted;
                                    bpMetadata.NucleotideRenderingProperties.SubstitutionCharacter = localNtSubstitution;
                                    bpMetadata.NucleotideRenderingProperties.CircleRendering = ntCircleRendering;
                                    bpMetadata.NucleotideRenderingProperties.TextRendering = ntTextRendering;
                                }

                                if(bpMetadata != null)
                                    bpProps.SetExtendedProperty(BasePairMetadata.Key, bpMetadata);
                            }
                        }
                    }
                }
            }
        }
    }
}
